/**
 * Tungsten: An Application Server for uni/cluster.
 * Copyright (C) 2011 Continuent Inc.
 * Contact: tungsten@continuent.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 *
 * Initial developer(s): Robert Hodges
 * Contributor(s):
 */

package com.sync.database;

/**
 * Implements a state machine-based algorithm to clean up MySQL queries for
 * regex parsing by eliminating leading white space, stripping ordinary
 * comments, and removing comment characters around MySQL '/*!NNNNN' style
 * comments generated by mysqldump.
 * 
 * @author <a href="mailto:robert.hodges@continuent.com">Robert Hodges</a>
 * @version 1.0
 */
public class MySQLOperationStringBuilder
{
    // Parameters.
    private int          maxOutputLength;

    // Input string and state thereof.
    private String       inputString;
    private int          inputLength;
    private int          inputIndex;

    // Output string buffer.
    private StringBuffer outputString = new StringBuffer();

    /** Create instance. */
    public MySQLOperationStringBuilder(int maxOutputLength)
    {
        this.maxOutputLength = maxOutputLength;
    }

    /** Build a string for parsing. */
    public String build(String input)
    {
        // Set up parameter for build operation.
        inputString = input;
        inputIndex = 0;
        inputLength = input.length();
        outputString = new StringBuffer();

        // Loop until we run out of input or output.
        char nextChar;
        while ((nextChar = get()) != 0
                && outputString.length() < maxOutputLength)
        {
            if (nextChar == '/')
            {
                if (inputStartsWith("*!"))
                {
                    // Look ahead to ensure we have a bang comment. 
                    String nextChars = peek(7);
                    boolean haveBangComment = false;
                    if (nextChars.length() == 7)
                    {
                        haveBangComment = true;
                        for (int i = 2; i < nextChars.length(); i++)
                        {
                            if (!Character.isDigit(nextChars.charAt(i)))
                            {
                                haveBangComment = false;
                                break;
                            }
                        }
                    }
                    
                    if (haveBangComment)
                    {
                        // Strip the enclosing comment characters and add content.
                        skip(7);
                        String contents = getToDelimiter("*/");
                        if (contents != null)
                        {
                            put(contents);
                            skip(2);
                        }
                    }
                    else
                    {
                        // No comment after all, so just add it. 
                        put(nextChar);
                    }
                }
                else if (inputStartsWith("*"))
                {
                    // Skip the entire comment. 
                    if (getToDelimiter("*/") != null)
                        skip(2);
                }
                else
                {
                    // Just add it. 
                    put(nextChar);
                }
            }
            else
            {
                put(nextChar);
            }
        }

        // Return what we found. 
        return outputString.toString();
    }

    // Returns the next character in the input string provided we
    // have one.
    private char get()
    {
        if (inputIndex < inputLength)
            return inputString.charAt(inputIndex++);
        else
            return 0;
    }

    // Preview the next N characters or return null if there are not enough of them. 
    private String peek(int n)
    {
        int endIndex = inputIndex + n;
        if (endIndex <= inputLength)
            return inputString.substring(inputIndex, endIndex);
        else
            return null;
    }

    // Return string characters up to but not including delimiter. 
    private String getToDelimiter(String delimiter)
    {
        String content = null;
        if (inputIndex < inputLength)
        {
            int delimiterIndex = inputString.indexOf(delimiter, inputIndex);
            if (delimiterIndex > -1)
            {
                content = inputString.substring(inputIndex, delimiterIndex);
                inputIndex = delimiterIndex;
            }
        }
        
        return content;
    }

    // Returns true if the input starts with the argument at the
    // current index.
    private boolean inputStartsWith(String prefix)
    {
        if (inputIndex < inputLength)
            return inputString.startsWith(prefix, inputIndex);
        else
            return false;
    }
    
    // Skip over the desired number of characters. 
    private void skip(int n)
    {
        if (inputIndex < inputLength)
            inputIndex += n;
    }

    // Adds a character to the output.
    private void put(char c)
    {
        if (! Character.isWhitespace(c) || outputString.length() != 0)
        {
            outputString.append(c);
        }
    }

    // Adds a string to the output.
    private void put(String s)
    {
        for (int i = 0; i < s.length(); i++)
            put(s.charAt(i));
    }
}